官方文件提到 Worklet 是輕量級的 web worker,使網路開發人員可以存取低階的渲染管道
MDN 上列出來的有三種,但其中的 AnimationWorklet 跟 LayoutWorklet 似乎都還在實驗階段,有興趣瞭解的人可以參考連結所附的介紹文章
而今天打算只介紹控制音訊的 AudioWorklet
在 AudioWorklet
出現之前,瀏覽器提供了 ScriptProcessorNode 供開發者在網頁上處理音訊,但使用 ScriptProcessorNode
操作音訊是執行在主線程上面的,因此有可能導致 UI 畫面的阻塞,所以在 Chrome 64 後才推出了 AudioWorklet
,AudioWorklet
運行在額外的音訊線程(AudioWorkletGlobalScope),避免了主線程資源的佔用
以下我們直接透過 Google AudioWorklet example,實際看看怎麼在網頁上操作音訊
範例中在按下按鈕後網頁會播放一聲 440Hz 的聲音,請大家在操作前先把聲音調小,不然可能會突然嚇到
建立 AudioContext
首先所有音訊操作的都需要在一個 音訊上下文中(AudioContext),所以一開始會先建立 AudioContext
,接著再按下 START 按鈕後執行 startAudio
const audioContext = new AudioContext();
window.addEventListener('load', async () => {
const buttonEl = document.getElementById('button-start');
buttonEl.disabled = false;
buttonEl.addEventListener('click', async () => {
await startAudio(audioContext);
audioContext.resume();
buttonEl.disabled = true;
buttonEl.textContent = 'Playing...';
}, false);
});
開始播放音訊
const startAudio = async (context) => {
await context.audioWorklet.addModule('bypass-processor.js');
const oscillator = new OscillatorNode(context);
const bypasser = new AudioWorkletNode(context, 'bypass-processor');
oscillator.connect(bypasser).connect(context.destination);
oscillator.start();
};
首先會呼叫 Worklet.addModule 將自定義的 bypass-processor.js (後面會再介紹到這個檔案) 以模組方式加載到 AudioContext
裡
await context.audioWorklet.addModule('bypass-processor.js');
接著呼叫 OscillatorNode,OscillatorNode
可以建構新的音訊波形,預設波形是正弦波(sine)、頻率是 440 Hz
// context - 使用的 AudioContext
// options - 可以設定波形、頻率等參數
const oscillator = new OscillatorNode(context, options);
例如:以下建立 660 Hz 的正弦波
const options = {
type: 'sine',
frequency: 660
}
const oscillator = new OscillatorNode(context, options);
下一步則是建立 AudioWorkletNode 實例,AudioWorkletNode
代表一個自定義的音訊節點,第二個參數就是我們之前加載進來的檔案名稱
const bypasser = new AudioWorkletNode(context, 'bypass-processor');
接著會將建立好的 OscillatorNode
連接到自定義的 bypasser(AudioWorkletNode)
,再連接到整個 AudioContext 的終點 (destination),這個終點可以視為播放聲音出來的設備,例如喇叭
oscillator.connect(bypasser).connect(context.destination);
最後呼叫 start 方法,開始播放音訊
oscillator.start();
上面這一連串使用到的方法,乍看之下似乎難以理解,但搭配以下這張 音訊路由圖 大概會有點感覺,整個音訊的操作都需要在黃色範圍內的 AudioContext
裡處理,而以上程式碼所做的事情大部分就是創立 音訊節點(AudioNode),並把各節點以 connect 的方式連接起來,當所有音訊節點的關聯性都連結好後,最後一步是呼叫 connect(context.destination)
,將所有節點連接到右下角的擴音設備,準備進行播放
在創建自訂音訊的時候有以下幾個步驟:
bypass-processor.js
AudioContext
中其中 1~3 步驟如以下所示,而 4~6 步驟是上面的 startAudio
函式中提過的
以下 bypass-processor.js
檔案負責處理自訂音訊,input
跟 output
分別對應輸入跟輸出的音源,其中的 BypassProcessor
單純複製輸入的音源到輸出,沒有做任何額外處理,process
方法的 回傳值 為 true 強制使 AudioWorkletNode
的狀態是 active
的
最後一行呼叫 registerProcessor
註冊 BypassProcessor
並給他一個名稱 'bypass-processor'
// bypass-processor.js
class BypassProcessor extends AudioWorkletProcessor {
process(inputs, outputs) {
// 預設只有單一個 input 跟 output.
const input = inputs[0];
const output = outputs[0];
// 單純複製輸入的音源到輸出,沒有做任何額外處理
for (let channel = 0; channel < output.length; ++channel) {
output[channel].set(input[channel]);
}
return true;
}
}
registerProcessor('bypass-processor', BypassProcessor);
結合以上程式碼,範例最終會輸出一個 440 Hz 的正弦波。
以上範例沒有額外處理音訊的輸入、輸出,但要寫出程式碼還是蠻複雜的,實在沒想到在網頁中處理音訊這麼困難
總之最後藉由這個範例大致上瞭解了 AudioWorklet
的用法,以及知道它可以使用額外獨立的線程處理音訊,避免影響到主線程 UI 的渲染